package com.blue.base.oauth.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.blue.base.common.constants.GatewayHeaderKey;
import com.blue.base.common.utils.date.LocalDateUtils;
import com.blue.base.common.utils.http.HttpContext;
import com.blue.base.oauth.common.bean.RefreshTokenRecord;
import com.blue.base.redis.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.function.Function;

/**
 * @author liulei
 * @version 1.0
 */
@Slf4j
public class OAuthTokenUtils {

    private static final String PREFIX = "cloud:refresh:record:";

    /**
     * 是否续签
     */
    private static boolean reNewToken = true;

    /**
     * 续签时间比例，当前剩余时间小于小于过期总时长的30%则续签
     */
    private static Double timeLeftDelayRatio = 0.3;

    /**
     * 生成key
     */
    public static String generateKey() {
        return PREFIX + HttpContext.getIpFromHeader(GatewayHeaderKey.REMOTE_IP);
    }

    /**
     * 生成获取授权码code的接口【授权码模式】
     *
     * @param request 请求
     * @return /oauth/authorize?参数=x&...
     */
    public static String getAuthorizeCodeUrl(HttpServletRequest request) {
        log.info("准备重定向......获取code：{}", HttpContext.getIp());
        String clientId = request.getParameter("clientId");
        String redirectUri = request.getParameter("redirectUri");
        String responseType = request.getParameter("responseType");
        String state = request.getParameter("state");
        String scope = request.getParameter("scope");

        return "/oauth/authorize?client_id=" + clientId
                + "&redirect_uri=" + redirectUri +
                "&response_type=" + responseType +
                "&state=" + state;
    }

    /**
     * 记录token的创建/刷新时间
     *
     * @param accessToken     token
     * @param tokenExpiration 过期时间
     */
    public static void recordAccessTokenCreate(String accessToken, Date tokenExpiration) {
        if (StringUtils.isEmpty(accessToken) || Objects.isNull(tokenExpiration)) {
            throw new RuntimeException("accessToken/tokenExpiration不能为空");
        }
        log.info("记录最近一次生成/刷新token成功的记录:{}", HttpContext.getIpFromHeader(GatewayHeaderKey.REMOTE_IP));
        RefreshTokenRecord record = RefreshTokenRecord.builder()
                .recordKey(generateKey())
                .accessToken(accessToken)
                .tokenExpires(tokenExpiration)
                .recordDate(System.currentTimeMillis())
                .build();
        // 默认存2个月
        RedisUtils.set(record.getRecordKey(), JSON.toJSONString(record), 60 * 60 * 24 * 30 * 2);
    }

    /**
     * 根据直接创建token标签，来决定token的生成/复用
     *
     * @param tokenType     token类型
     * @param createNewFlag 直接创建token标签
     */
    public static String getAccessTokenByFlag(String tokenType, boolean createNewFlag) {
        String recordKey = generateKey();
        if (!createNewFlag && RedisUtils.hasKey(recordKey) && "session".equals(tokenType)) {
            // session模式
            JSONObject jsonObject = JSON.parseObject(RedisUtils.get(recordKey));
            RefreshTokenRecord record = JSON.toJavaObject(jsonObject, RefreshTokenRecord.class);
            // 判断accessToken是否过期
            if (record.getTokenExpires().after(new Date())) {
                log.info("token【{}】尚未过期，预计【{}】过期，刷新后重新计算将过期时间：当前时间+数据库token存活时间",
                        record.getAccessToken(), record.getExpiresDescribe());
                return record.getAccessToken();
            }
        }
        log.info("决策判断出，即将使用全新的token");
        return UUID.randomUUID().toString();
    }

    /**
     * token续签
     *
     * @param oAuth2AccessToken accessToken
     * @param tokenStore        tokenStore
     * @param function          获取token有效期时长(秒)
     * @return
     */
    public static OAuth2AccessToken delayAccessToken(OAuth2AccessToken oAuth2AccessToken, TokenStore tokenStore,
                                                     Function<OAuth2Request, Integer> function) {
        //是否开启token续签
        if (reNewToken && oAuth2AccessToken != null) {
            OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(oAuth2AccessToken.getValue());
            OAuth2Request clientAuth = auth2Authentication.getOAuth2Request();
            //访问令牌的原始有效期，单位秒
            int validitySeconds = function.apply(clientAuth);
            if (validitySeconds > 0) {
                double expiresRatio = oAuth2AccessToken.getExpiresIn() / (double) validitySeconds;
                //判断是否需要续签，当前剩余时间小于过期时长的30%则续签
                if (expiresRatio < timeLeftDelayRatio) {
                    //更新AccessToken过期时间为2小时后
                    DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) oAuth2AccessToken;
                    // 将令牌给续满至初始化值
                    long delayMillSeconds = validitySeconds - oAuth2AccessToken.getExpiresIn();
                    defaultOAuth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + delayMillSeconds * 1000));
                    tokenStore.storeAccessToken(defaultOAuth2AccessToken, auth2Authentication);
                    log.info("返回续签的token:{},延迟至:{}过期", defaultOAuth2AccessToken,
                            LocalDateUtils.dateConvertToString(defaultOAuth2AccessToken.getExpiration()));
                    return defaultOAuth2AccessToken;
                }
            }
            log.info("token续签判断:{},持续走自然失效", false);
        }
        return null;
    }
}
